home *** CD-ROM | disk | FTP | other *** search
- /* scrollMenuBarPatch.c 18 Nov 1987
- *
- * by Mike Scanlin and Andy Voelker
- *
- * This will install a patch to _GetNextEvent that intercepts mouseDown events if they occur
- * in the very top pixel-row of the screen. If an event is intercepted, the menu bar is scrolled
- * off the right side of the screen before the user can react. After 4 more clicks in the menuBar
- * (with appropriately sarcastic messages displayed after each one) the original menuBar is
- * restored. The patch can be removed by typing COMMAND-OPTION-SHIFT-TAB.
- */
-
- #include "EventMgr.h"
- #include "QuickDraw.h"
- #include "asm.h"
-
- /* low memory globals */
- extern Handle MenuList : 0x0A1C;
- extern Ptr ScrnBase : 0x0824;
- extern GrafPtr WMgrPort : 0x09DE;
-
- /* the traps we patch */
- #define GetNextEventTrap 0xA970
- #define DrawMenuBarTrap 0xA937
- #define HiliteMenuTrap 0xA938
-
- #define CLICKS_TO_MENU 4
- #define MESSAGE_DELAY 60
- #define MENU_BAR_HEIGHT 20
- #define TAB_KEY 0x09
- #define JMP_INSTRUCTION 0x4EF9
-
- #define memFullErr -108
- #define screenBits_bounds_right -110
- #define screenBits_bounds_left -114
-
- void main(void);
-
- void main()
- {
- asm {
-
- /* This first section is the only part that gets run initially. It gets some space in the system heap
- * and sets up a patch to _GetNextEvent. The patch won't do anything until the user clicks in the
- * very top pixel of the menu bar (in which case it will scroll the menu bar off the screen to
- * the right). */
-
- move.l D3,-(SP)
-
- /* get the old trap address */
- move #GetNextEventTrap,D0
- _GetTrapAddress
-
- /* set the address for the JMP instruction that calls the original trap */
- lea @origTrap,A1
- move.l A0,(A1)
-
- /* get some space in the system heap for our patch */
- lea @last,A0
- lea @first,A1
- suba.l A1,A0
- move.l A0,D0 /* D0 = length of patch */
- move.l D0,D3 /* save for _BlockMove */
- _NewPtr SYS
- cmpi #memFullErr,D0
- beq.s @noPatch
- lea @saveLoc,A1
- move.l A0,(A1) /* save for removePatch */
- move.l A0,-(SP) /* save for _BlockMove */
-
- /* set the trap address to the space we just got in the system heap. */
- move #GetNextEventTrap,D0
- _SetTrapAddress
-
- /* now move our patch into place */
- lea @first,A0
- move.l (SP)+,A1 /* result from _NewPtr */
- move.l D3,D0
- _BlockMove
-
- @noPatch
- move.l (SP)+,D3
-
- /* this is the end of the installation part, but we can't do an
- * RTS here because LSC needs to clean up. So we fall through
- * to the end. */
- bra @last
-
- /**********************************************
- * Here's the new _GetNextEvent. It calls the existing _GetNextEvent
- * and then checks if a mouseDown or keyDown event is being reported.
- * If not, the event is passed to the application unmodified. If we end up
- * using the event ourselves, a null event is returned to the application.
- **********************************************/
-
- @first
- /* pop the original return address and save it */
- lea @exitAddress,A0
- move.l (SP)+,(A0)
- /* save ptr to event record so we can get at it later */
- lea @eventRecPtr,A0
- move.l (SP),(A0)
- /* set the return address to our patch */
- pea @tailPatch
-
- /* the nops get filled with the address of the original _GetNextEvent */
- dc JMP_INSTRUCTION
- @origTrap
- nop
- nop
-
- /* this is where it comes after the normal _GetNextEvent processing */
- @tailPatch
- movem.l A1/D0-D3,-(SP)
-
- lea @eventRecPtr,A0
- move.l (A0),A0
-
- /* check if it's a keydown event that says to remove ourself. This is the only keyDown
- * that we intercept. */
- move OFFSET(EventRecord,what)(A0),D0
- cmpi #keyDown,D0
- bne.s @noKeyDown
- /* the key to remove the patch is COMMAND-SHIFT-OPTION-Tab */
- move.l OFFSET(EventRecord,message)(A0),D0
- cmpi.b #TAB_KEY,D0
- bne.s @noKeyDown
- move OFFSET(EventRecord,modifiers)(A0),D0
- andi #optionKey + cmdKey + shiftKey,D0
- eori #optionKey + cmdKey + shiftKey,D0
- beq.s @removePatch
-
- @noKeyDown
- /* if it's not a mousedown event, then ignore it */
- cmpi #mouseDown,D0
- bne.s @patchExit
-
- /* if we've already scrolled the menu list, then don't scroll it again */
- lea @menus,A1
- tst (A1)
- bne.s @alreadyGone
-
- /* if no menus exist, then leave */
- move.l MenuList,A1
- move.l (A1),A1
- tst (A1)
- beq.s @patchExit
-
- /* if the mouse is not at an odd location, then leave */
- move.l OFFSET(EventRecord,where)(A0),D0
- andi #1,D0
- beq.s @patchExit
- /* if the mouse is not at the very top pixel of the menu bar, then leave */
- move.l OFFSET(EventRecord,where)(A0),D0
- /* put vertical coordinate in low word */
- swap D0
- cmpi #1,D0
- bge.s @patchExit
-
- /* now we're set to scroll that puppy. Do it... */
- bsr @scrollMenuBar
-
- /* save the fact that the menu bar that was just scrolled */
- lea @menus,A0
- move #1,(A0)
-
- /* patch _DrawMenuBar and _HiliteMenu to do nothing if and when they're called */
- bsr @disableTraps
-
- lea @clicks,A1
- move #CLICKS_TO_MENU,(A1)
-
- bra.s @returnNullEvent
-
- @alreadyGone
- /* if mouse is not in menu bar, then leave */
- move.l OFFSET(EventRecord,where)(A0),D0
- /* put vertical coordinate in low word */
- swap D0
- cmpi #MENU_BAR_HEIGHT,D0
- bge.s @patchExit
-
- /* print a message in the menu bar */
- bsr @drawAMessage
-
- lea @clicks,A0
- subi #1,(A0)
- bne.s @returnNullEvent
-
- /* now that we're finished playing, restore _DrawMenuBar and _HiliteMenu */
- bsr @restoreMenus
-
- @returnNullEvent
- /* set the event to null */
- lea @eventRecPtr,A0
- move.l (A0),A0
- clr OFFSET(EventRecord,what)(A0)
- /* change _GetNextEvent's return value to false.
- * the 20 is for the 5 regs that are saved on the stack at this point */
- clr 20(SP)
-
- @patchExit
- movem.l (SP)+,A1/D0-D3
- /* JMP to the place that called _GetNextEvent */
- dc JMP_INSTRUCTION
- @exitAddress
- nop
- nop
-
-
- /* this is where it comes to remove the _GetNextEvent patch */
- @removePatch
- /* if the menus aren't shown, then restore them before leaving */
- bsr @restoreMenus
- /* check if we are the most recent patch to _GetNextEvent.
- * If we're not, then don't unpatch. */
- move #GetNextEventTrap,D0
- _GetTrapAddress
- lea @first,A1
- cmpa.l A1,A0
- bne.s @returnNullEvent
- /* set the trap address back to the original trap. */
- lea @origTrap,A0
- move.l (A0),A0
- move #GetNextEventTrap,D0
- _SetTrapAddress
- /* beep to let them know it has been removed */
- move #1,-(SP)
- _SysBeep
- /* free up the mem occupied by this patch */
- lea @saveLoc,A0
- move.l (A0),A0
- _DisposPtr
- bra.s @returnNullEvent
-
-
- /* This routine will disable _DrawMenuBar and _HiliteMenu */
- @disableTraps
- move #DrawMenuBarTrap,D0
- _GetTrapAddress
- lea @drawMenuBarAddr,A1
- move.l A0,(A1)
- lea @doNothing,A0
- move #DrawMenuBarTrap,D0
- _SetTrapAddress
- move #HiliteMenuTrap,D0
- _GetTrapAddress
- lea @hiliteMenuAddr,A1
- move.l A0,(A1)
- lea @doNothingWithParam,A0
- move #HiliteMenuTrap,D0
- _SetTrapAddress
- rts
-
-
- /* This routine will restore _DrawMenuBar and _HiliteMenu and
- * then draw the current menu bar */
- @restoreMenus
- lea @menus,A0
- tst (A0)
- beq.s @doNothing
- /* unpatch DrawMenuBar and then draw the old menu bar */
- lea @drawMenuBarAddr,A0
- move.l (A0),A0
- move #DrawMenuBarTrap,D0
- _SetTrapAddress
- lea @hiliteMenuAddr,A0
- move.l (A0),A0
- move #HiliteMenuTrap,D0
- _SetTrapAddress
- lea @menus,A0
- clr (A0)
- _DrawMenuBar
- @doNothing
- rts
-
-
- /* This is the _HiliteMenu routine that does nothing. It gets rid of the parameter passed
- * to _HiliteMenu and then returns. */
- @doNothingWithParam
- move.l (SP)+,A0
- addq.l #2,SP
- jmp (A0)
-
-
- /* This is the routine that actually does the scrolling */
- @scrollMenuBar
- /* hide the cursor so we don't get part of the cursor scrolled with the menuBar */
- _HideCursor
-
- /* get the current screen width from the Quickdraw global screenBits */
- move.l (A5),A0
- /* D3 = # of columns (width) - 1 in the current screen */
- move screenBits_bounds_right(A0),D3
- sub screenBits_bounds_left(A0),D3
- move D3,D2
- subq #1,D3
- /* D2 = # of bytes - 1 in one row of screen */
- asr #3,D2
- subq #1,D2
-
- /* here's the loop that actually scrolls the menu bar all the way across the screen */
- @wayOut
- move #MENU_BAR_HEIGHT - 2,D1
-
- /* this outside loop will scroll all rows of the menu bar one pixel to the right */
- @outside
- move D2,D0
- addq #1,D0
- mulu D1,D0 /* calc # of bytes from base addr to start of current row */
- move.l ScrnBase,A0
- adda.l D0,A0
-
- clr.b (A0) /* white-out the left edge of this row */
-
- move D2,D0 /* D0 = number of words in one row */
- asr #1,D0
-
- andi.b #0xEF,CCR /* set the X flag */
- /* this inner loop will scroll one row of the screen one pixel to the right */
- @inside
- roxr (A0)+
- dbra D0,@inside
-
- /* go and do the next row */
- dbra D1,@outside
-
- /* make the corners look like they used to (i.e. rounded and black) */
- move D2,D0
- move.l ScrnBase,A1
- ori.b #0xF8,(A1)
- adda D0,A1
- ori.b #0x1F,(A1)+
- ori.b #0xE0,(A1)
- adda D0,A1
- ori.b #0x07,(A1)+
- ori.b #0xC0,(A1)
- adda D0,A1
- ori.b #0x03,(A1)+
- ori.b #0x80,(A1)
- adda D0,A1
- ori.b #0x01,(A1)+
- ori.b #0x80,(A1)
- adda D0,A1
- ori.b #0x01,(A1)
-
- dbra D3,@wayOut
-
- _ShowCursor
- rts
-
-
- /* this routine will print a string in the menu bar, wait a bit and then erase it */
- @drawAMessage
- /* set the current port to the window manager's */
- move.l (A5),A0
- lea @savePort,A1
- move.l (A0),(A1)
- move.l WMgrPort,(A0)
-
- /* save the old clipRgn */
- lea @saveClip,A0
- move.l WMgrPort,A1
- move.l OFFSET(GrafPort,clipRgn)(A1),(A0)
- /* set the clipRgn to be big enough for the string we want to print */
- subq #4,SP
- _NewRgn
- move.l WMgrPort,A1
- move.l (SP)+,OFFSET(GrafPort,clipRgn)(A1)
- pea @stringRect
- _ClipRect
-
- /* the string we print depends on the value of the clicks variable */
- lea @clicks,A0
- move (A0),D0
- cmpi #4,D0
- bne.s @1
- pea @string1
- bra.s @5
- @1 cmpi #3,D0
- bne.s @2
- pea @string2
- bra.s @5
- @2 cmpi #2,D0
- bne.s @3
- pea @string3
- bra.s @5
- @3 cmpi #1,D0
- bne.s @delay
- pea @string4
- @5 move #10,-(SP)
- move #14,-(SP)
- _MoveTo
- _DrawString
- @delay
- lea @downTime,A0
- move.l Ticks,(A0)
-
- /* wait until the mouse is released or 60 ticks, which ever is longer */
- @waitTilMouseUp
- subq.l #2,SP
- _StillDown
- tst (SP)+
- bne.s @waitTilMouseUp
- lea @downTime,A0
- move.l (A0),D0
- addi.l #MESSAGE_DELAY,D0
- cmp.l Ticks,D0
- bgt.s @waitTilMouseUp
-
- /* erase the string */
- pea @stringRect
- _EraseRect
-
- /* dispose of the clipRgn we created */
- move.l WMgrPort,A1
- move.l OFFSET(GrafPort,clipRgn)(A1),-(SP)
- _DisposeRgn
- /* restore the old clip rgn */
- lea @saveClip,A0
- move.l WMgrPort,A1
- move.l (A0),OFFSET(GrafPort,clipRgn)(A1)
-
- /* restore the old port */
- move.l (A5),A0
- lea @savePort,A1
- move.l (A1),(A0)
- rts
-
-
- @eventRecPtr dc.l 0
- @menus dc 0 /* TRUE while menus that exist are not being shown */
- @downTime dc.l 0
- @clicks dc 0 /* number of menus clicks until menus return */
- @words dc 0 /* number of 16 bit words in one row of screen */
- @saveLoc dc.l 0 /* address of our patch in the system heap */
- @savePort dc.l 0
- @saveClip dc.l 0
- @drawMenuBarAddr dc.l 0
- @hiliteMenuAddr dc.l 0
-
- /* this rect should be big enough to enclose the longest of the strings. */
- @stringRect dc 0,8,MENU_BAR_HEIGHT - 1,300
-
- /* define the pascal strings. Does LSC provide a better way than this? */
- @string1 dc.b 13,'N','o',' ','M','e','n','u','s',' ','H','e','r','e'
- @string2 dc.b 14,'S','t','i','l','l',' ','N','o',' ','M','e','n','u','s'
- @string3 dc.b 7,'N','o','t',' ','Y','e','t'
- @string4 dc.b 25,'W','h','e','r','e',' ','D','i','d',' ','T','h','o','s','e'
- dc.b ' ','M','e','n','u','s',' ','G','o','?'
- @last
- }
- }
-